home *** CD-ROM | disk | FTP | other *** search
/ Programmer Power Tools / Programmer Power Tools.iso / rbbs_pc / 173_asm.arc / BDRIVEC2.ASM < prev    next >
Assembly Source File  |  1983-08-04  |  24KB  |  904 lines

  1. TITLE  DRIVEIO
  2. ;
  3. ; --- CORVUS/IBM DRIVE INTERFACE UNIT FOR MICROSOFT ---
  4. ;          PASCAL AND BASIC COMPILERS
  5. ;      CONST ][ VERSION FOR DOS 1.10 & 2.0
  6. ;
  7. ;        VERSION 1.41  BY  BRK
  8. ;       (MICROSOFT ASSEMBLER VERSION )
  9. ;
  10. ;
  11. ;   NOTE: THIS INTERFACE UNIT NOW SUPPORTS BOTH PASCAL AND BASIC
  12. ;      COMPILERS BUT IT MUST BE RE-ASSEMBLED WITH THE APPROPRIATE
  13. ;      SETTING OF THE  "LTYPE"  EQUATE TO DO THIS FOR EACH LANGUAGE.
  14. ;
  15. ;
  16. ;
  17. ;    THIS UNIT IMPLEMENTS  9  PROCEDURES:
  18. ;
  19. ;    INITIO
  20. ;    BIOPTR        - CONST. ][
  21. ;    SETSRVR        - CONST. ][
  22. ;    FINDSRVR    - CONST. ][
  23. ;    NETCMD        - CONST. ][
  24. ;    CDRECV = DRVRECV
  25. ;    CDSEND = DRVSEND
  26. ;
  27. ;    THE CALLING PROCEDURE IN PASCAL IS :
  28. ;
  29. ;        CDSEND (VAR st : longstring )
  30. ;
  31. ;    THE FIRST TWO BYTES OF THE STRING ARE THE LENGTH
  32. ;    OF THE STRING TO BE SENT OR THE LENGTH OF THE 
  33. ;    STRING RECEIVED.
  34. ;
  35. ;        function INITIO    : INTEGER
  36. ;
  37. ;    THE FUNCTION RETURNS A VALUE TO INDICATE THE STATUS OF
  38. ;    THE INITIALIZATION OPERATION.  A VALUE OF ZERO INDICATES
  39. ;    THAT THE INITIALIZATION WAS SUCCESSFUL.  A NON-ZERO VALUE
  40. ;    INDICATES THE I/O WAS NOT SETUP AND THE CALLING PROGRAM
  41. ;    SHOULD NOT ATTEMPT TO USE THE CORVUS DRIVERS.
  42. ;
  43. ;        function BIOPTR    : INTEGER
  44. ;
  45. ;    THE FUNCTION RETURNS A 16 BIT POINTER TO THE "CORTAB"
  46. ;    BIOS TABLE IN THE CORVUS "BIOS" DRIVERS.  THIS ROUTINE
  47. ;    SHOULD NOT BE EXECUTED BEFORE A SUCCESSFUL USE OF THE
  48. ;    "INITIO" ROUTINE (ABOVE).  NOTE:  THE RETURNED VALUE IS
  49. ;    RELATIVE TO "SEGMENT" ZERO, AND A RETURNED VALUE OF ZERO
  50. ;    INDICATES THAT THE "CORTAB" TABLE COULD NOT BE FOUND.
  51. ;
  52. ;        function SETSRVR ( srvr : integer): INTEGER
  53. ;
  54. ;    THE FUNCTION RETURNS THE "BOOT SERVER" NETWORK ADDRESS.
  55. ;    IF THE INPUT PARAMETER IS LESS THAN  255 ( BUT NOT NEGATIVE ),
  56. ;    IT WILL BE TAKEN AS A RESET OF THE DEFAULT SERVER # WHEN
  57. ;    USING THE  SEND & RECIEVE  ROUTINES.  IF IT IS GREATER THAN 255
  58. ;    OR NEGATIVE, NO CHANGE OF THE DEFAULT SERVER # WILL BE MADE.
  59. ;    NOTE: THE DEFAULT SERVER # IS AUTOMATICALLY SET TO THE
  60. ;    BOOT SERVER # WHEN THE  "INITIO" FUNCTION IS EXECUTED.
  61. ;    
  62. ;        function FINDSRVR : INTEGER
  63. ;
  64. ;    THE FUNCTION RETURNS THE NETWORK ADDRESS OF A VALID DISK SERVER.
  65. ;    IF THE RETURNED VALUE IS GREATER THAN 63 OR NEGATIVE, THE COMMAND
  66. ;    FAILED TO FIND A SERVER ( THE FLAT CABLE CARDS WOULD DO THIS ).
  67. ;
  68. ;        function CARDID : INTEGER
  69. ;
  70. ;    THE FUNCTION RETURNS THE CORVUS INTERFACE CARD TYPE ( 0 - OMNINET,
  71. ;    1 - FLAT CABLE ).
  72. ;
  73. ;        function NETCMD ( VAR inp, VAR out: longstring) : INTEGER
  74. ;
  75. ;    THE FUNCTION IS USED TO SEND/RECIEVE DATA TO A NETWORK SERVER.
  76. ;    STRING  inp  SPECIFIES THE COMMAND TO SEND TO THE SERVER,
  77. ;    AND STRING  out  IS WHERE ANY RETURNED DATA WILL BE PLACED
  78. ;    ( THE STRING LENGTH OF  out  WILL NOT BE CHANGED BY THIS
  79. ;    OPERATION UNLESS THE COMMAND FAILED- IN WHICH CASE THE LENGTH
  80. ;    WILL BE SET TO ZERO).  THE VALUE OF THE FUNCTION WILL BE
  81. ;    RETURNED AS  ZERO  IF THE OPERATION WAS SUCCESSFUL, AND
  82. ;    NON-ZERO IF IT FAILED.
  83. ;    NOTE: THE SERVER # USED WILL BE THE "BOOT SERVER" # UNLESS
  84. ;    THE DEFAULT IS CHANGED BY THE  "SETSRVR" CMD.
  85. ;
  86. ;
  87. ;
  88. ;
  89. ;    THE CALLING PROCEDURE BASIC IS :
  90. ;
  91. ;        CALL CDSEND (B$ )
  92. ;
  93. ;    THE FIRST TWO BYTES OF THE STRING ARE THE LENGTH
  94. ;    OF THE STRING TO BE SENT OR THE LENGTH OF THE 
  95. ;    STRING RECEIVED ( I.E. LEFT$(B$,2) ).
  96. ;
  97. ;        CALL INITIO (A%)
  98. ;
  99. ;    THE FUNCTION RETURNS A VALUE TO INDICATE THE STATUS OF
  100. ;    THE INITIALIZATION OPERATION.  A VALUE OF ZERO INDICATES
  101. ;    THAT THE INITIALIZATION WAS SUCCESSFUL.  A NON-ZERO VALUE
  102. ;    INDICATES THE I/O WAS NOT SETUP AND THE CALLING PROGRAM
  103. ;    SHOULD NOT ATTEMPT TO USE THE CORVUS DRIVERS.
  104. ;
  105. ;        CALL BIOPTR (A%)
  106. ;
  107. ;    THE FUNCTION RETURNS A 16 BIT POINTER TO THE "CORTAB"
  108. ;    BIOS TABLE IN THE CORVUS "BIOS" DRIVERS.  THIS ROUTINE
  109. ;    SHOULD NOT BE EXECUTED BEFORE A SUCCESSFUL USE OF THE
  110. ;    "INITIO" ROUTINE (ABOVE).  NOTE:  THE RETURNED VALUE IS
  111. ;    RELATIVE TO "SEGMENT" ZERO, AND A RETURNED VALUE OF ZERO
  112. ;    INDICATES THAT THE "CORTAB" TABLE COULD NOT BE FOUND.
  113. ;
  114. ;        CALL SETSRVR (A%)     here  A% is used for input and output
  115. ;
  116. ;    THE FUNCTION RETURNS THE "BOOT SERVER" NETWORK ADDRESS.
  117. ;    IF THE INPUT PARAMETER IS LESS THAN  255 ( BUT NOT NEGATIVE ),
  118. ;    IT WILL BE TAKEN AS A RESET OF THE DEFAULT SERVER # WHEN
  119. ;    USING THE  SEND & RECIEVE  ROUTINES.  IF IT IS GREATER THAN 255
  120. ;    OR NEGATIVE, NO CHANGE OF THE DEFAULT SERVER # WILL BE MADE.
  121. ;    NOTE: THE DEFAULT SERVER # IS AUTOMATICALLY SET TO THE
  122. ;    BOOT SERVER # WHEN THE  "INITIO" FUNCTION IS EXECUTED.
  123. ;    
  124. ;        CALL FINDSRVR (A%)
  125. ;
  126. ;    THE FUNCTION RETURNS THE NETWORK ADDRESS OF A VALID DISK SERVER.
  127. ;    IF THE RETURNED VALUE IS GREATER THAN 63 OR NEGATIVE, THE COMMAND
  128. ;    FAILED TO FIND A SERVER ( THE FLAT CABLE CARDS WOULD DO THIS ).
  129. ;
  130. ;        CALL CARDID (A%)
  131. ;
  132. ;    THE FUNCTION RETURNS THE CORVUS INTERFACE CARD TYPE ( 0 - OMNINET,
  133. ;    1 - FLAT CABLE ).
  134. ;
  135. ;        CALL NETCMD ( A$,B$,C%)
  136. ;
  137. ;    THE FUNCTION IS USED TO SEND/RECIEVE DATA TO A NETWORK SERVER.
  138. ;    STRING  A$  SPECIFIES THE COMMAND TO SEND TO THE SERVER,
  139. ;    AND STRING  B$  IS WHERE ANY RETURNED DATA WILL BE PLACED
  140. ;    ( THE STRING LENGTH OF  out  WILL NOT BE CHANGED BY THIS
  141. ;    OPERATION UNLESS THE COMMAND FAILED- IN WHICH CASE THE LENGTH
  142. ;    WILL BE SET TO ZERO).  THE VALUE OF THE FUNCTION WILL BE
  143. ;    RETURNED ( IN C% ) AS  ZERO  IF THE OPERATION WAS SUCCESSFUL, AND
  144. ;    NON-ZERO IF IT FAILED.
  145. ;    NOTE: THE SERVER # USED WILL BE THE "BOOT SERVER" # UNLESS
  146. ;    THE DEFAULT IS CHANGED BY THE  "SETSRVR" CMD.
  147. ;
  148. ;=============================================================
  149. ;            REVISION HISTORY
  150. ;
  151. ; FIRST VERSION : 10-05-82  BY BRK
  152. ;         : 11-01-82  improved turn around delay for mirror
  153. ;        : 02-16-83  CONST. ][ version
  154. ;        : 05-16-83  added support for Basic
  155. ;        : 07-06-83  fixed bug in FINDSRVR routine
  156. ; V1.40        : 07-29-83  updated for DOS 2.0 
  157. ; V1.41        : 08-04-83  set timeout to zero to avoid ROM bug
  158. ;
  159. ;=============================================================
  160. ;
  161. TRUE    EQU    0FFFFH
  162. FALSE    EQU    0
  163. ;
  164. PASCAL    EQU    1    ; LANGUAGE TYPE DESCRIPTOR
  165. BASIC    EQU    2    ; LANGUAGE TYPE DESCRIPTOR
  166. ;
  167. LTYPE    EQU    PASCAL    ; SET TO LANGUAGE TYPE TO BE USED WITH
  168. INTDVR    EQU    FALSE    ; SET TO FALSE TO DISABLE INTERNAL FLAT CABLE DRIVER
  169. ;
  170. ;
  171. ; ----- CORVUS EQUATES -----
  172. ;
  173. DATA    EQU    2EEH    ; DISC I/O PORT #
  174. STAT    EQU    2EFH    ; DISC STATUS PORT
  175. DRDY    EQU    1    ; MASK FOR DRIVE READY BIT
  176. DIFAC    EQU    2    ; MASK FOR BUS DIRECTION BIT
  177. ROMSEG    EQU    0DF00H    ; LOCATION OF CORVUS ROM
  178. BIOSSEG    EQU    60H    ; STD IBM BIOS SEGMENT ADDRESS
  179. ABTCTR    EQU    0A00H    ; VALUE TO SET TIMEOUT AND # OF RETRYS
  180. ;            ;   v1.41  timeouts=0
  181. ;
  182. FCALL    EQU    9AH    ; OPCODE FOR FAR CALL
  183. FJMP    EQU    0EAH    ; OPCODE FOR FAR JUMP
  184. ;
  185. ; --- MSDOS EQUATES ( V2.0 ) ---
  186. ;
  187. VERCMD    EQU    30H    ; BDOS COMMAND TO GET VERSION #
  188. HOPEN    EQU    3DH    ; BDOS COMMAND TO "OPEN" A FILE HANDLE
  189. HCLOSE    EQU    3EH    ; BDOS COMMAND TO "CLOSE" A FILE HANDLE
  190. HREAD    EQU    3FH    ; BDOS COMMAND TO "READ" FROM A FILE
  191. HWRITE    EQU    40H    ; BDOS COMMAND TO "WRITE" TO A FILE
  192. ;
  193. PGSEG    SEGMENT 'CODE'
  194.     ASSUME    CS:PGSEG
  195. ;
  196. ;
  197.     IF    LTYPE EQ PASCAL
  198.     DB    'CORVUS/IBM PC CONST. ][ PASCAL DRIVER AS OF 08-04-83'
  199.     ENDIF
  200. ;
  201.     IF    LTYPE EQ BASIC
  202.     DB    'CORVUS/IBM PC CONST. ][ BASIC  DRIVER AS OF 08-04-83'
  203.     ENDIF
  204. ;
  205. ; --- COPY OF "ROM" FAR JUMP TABLE ---
  206. ;
  207. ROMTAB    PROC    NEAR
  208.     DB    FJMP
  209.     DW    0,ROMSEG    ; FAR JUMP TO COLD BOOT ROM ENTRY
  210.     DB    FJMP
  211.     DW    3,ROMSEG    ; FAR JUMP TO WARM START ROM ENTRY
  212.     DB    FJMP
  213.     DW    6,ROMSEG    ; FAR JUMP TO I/O ROM ENTRY
  214.     DB    FJMP
  215.     DW    9,ROMSEG    ; FAR JUMP TO DUMMY "IRET" ENTRY
  216. LENTAB    EQU    offset $-offset ROMTAB    ; LENGTH OF TABLE
  217. ROMTAB    ENDP
  218. ;
  219. ; --- COPY OF CORVUS TABLE IDENTIER ---
  220. ;
  221. CORTAB    DB    'CORTAb'    ; VERSION FOR CONST. ][
  222. ;
  223. ; --- COPY OF UTILITY "HOOK" DRIVER NAME ---
  224. ;
  225. UTILPTR    DB    'UTILHOOK',0
  226. ;
  227. ;
  228. ; --- THESE DATA POINTERS MUST BE KEPT IN THE SAME RELATIVE ORDER
  229. ;
  230. SNDPTR    DW    0        ; BUFFER TO SAVE POINTER TO 'SEND' STRING
  231. SNDSEG    DW    0        ; BUFFER TO SAVE 'SEND' STRING SEGMENT #
  232. ;
  233. CORVEC    DW    0,0        ; BUF TO SAVE DOUBLE WORD POINTER TO "CORTAB"
  234. ;
  235. ; --- MISC DATA AND BUFFERS ----
  236. ;
  237. CORPTR    DW    0        ; BUFFER FOR "CORTAB" POINTER
  238. ;                ;  INITIALIZE INITIALLY TO ZERO
  239. CRDTYPE    DB    1        ; BUFFER TO SAVE "CARD TYPE" BYTE
  240. BOOTSRVR DB    0FFH        ; BUFFER FOR "BOOT SERVER"
  241. SRVR    DB    0FFH        ; BUFFER FOR "DEFAULT SERVER"
  242. ;
  243. ;
  244. ; === INITIALIZE CORVUS I/O DRIVERS ===
  245. ;
  246. ;    THIS ROUTINE MUST BE CALLED
  247. ;    ONCE TO SETUP THE DRIVERS BEFORE
  248. ;    THEY ARE USED. IF THE ROUTINE DOES
  249. ;    ANYTHING THAT CAN ONLY BE DONE ONCE,
  250. ;    IT MUST DISABLE THIS SECTION SO THAT
  251. ;    AND ACCIDENTAL SECOND CALL WILL NOT
  252. ;    LOCK UP THE HARDWARE.
  253. ;
  254.     PUBLIC INITIO
  255. ;
  256. INITIO    PROC    FAR
  257.     PUSH    DS
  258.     PUSH    ES
  259.     PUSH    CS
  260.     POP    ES        ; SET ES=CS
  261.     CLD
  262. ;
  263.     MOV    AH,VERCMD    ; MSDOS VERSION CHECK COMMAND
  264.     INT    21H        ; GET VERSION # OF DOS
  265.     OR    AL,AL        ; IS IT V 1.1 OR 1.0?
  266.     JZ    IV11        ; YES, SO TRY FINDING "CORTAb"
  267. ;
  268.     PUSH    CS
  269.     POP    DS        ; SET TO LOCAL SEGMENT FOR TESTING
  270. ;
  271.     MOV    AH,HOPEN    ; SET MSDOS 2.X, OPEN HANDLE COMMAND
  272.     MOV    AL,2        ; OPEN FOR R/W
  273.     MOV    DX,offset UTILPTR ; POINT TO "HOOK" DRIVER NAME
  274.     INT    21H        ; DO IT
  275.     JC    IV12        ; IF ERROR, TRY FOR IBM ROM
  276. ;
  277.     MOV    BX,AX        ; GET "HANDLE" IN (BX)
  278.     MOV    AH,HWRITE    ; GET WRITE CMD
  279.     MOV    CX,2        ; SET TO WRITE 2 CHARS
  280.     MOV    DX,offset UTILPTR ; USE NAME FOR SOURCE OF CHARACTERS
  281.     INT    21H        ; THIS SHOULD RESET "POINTER" IN DRIVER
  282. ;
  283.     MOV    AH,HREAD    ; SET READ CMD
  284.     MOV    CX,4        ; SET TO READ  DOUBLE WORD
  285.     MOV    DX,offset CORVEC ; POINT TO DESTINATION OF READ
  286.     INT    21H        ; DO IT
  287. ;
  288.     MOV    AH,HCLOSE    ; GET CLOSE CMD
  289.     INT    21H        ; CLOSE HANDLE
  290. ;
  291.     LDS    BX,dword ptr CORVEC ; GET POSSIBLE POINTER TO "CORTAb"
  292.     CALL    BIOT1        ; TEST FOR "CORTAb"
  293.     JNC    OKEXIT        ; IF OK, EXIT
  294.     JMP    IV12        ; OTHERWISE PROCEED
  295. ;
  296. IV11:    MOV    AX,BIOSSEG    ; SET TO TEST STD IBM SEGMENT ADD
  297.     CALL    BIOTST        ; TEST BIOS AND LINK TO IT IF OK
  298.     JNC    OKEXIT        ; IF OK, EXIT
  299.     MOV    AX,BIOSSEG-20H    ; TRY MICROSOFT STD LOCATION (40H)
  300.     CALL    BIOTST
  301.     JNC    OKEXIT        ; IF OK, EXIT
  302. ;
  303. IV12:    MOV    AX,ROMSEG
  304.     MOV    DS,AX        ; SET DS=ROM SEGMENT
  305.     XOR    AX,AX        ; GET A ZERO
  306.     MOV    BX,AX        ; POINT TO START OF ROM
  307.     MOV    DI,AX        ; INIT CHECKSUM COUNTER
  308.     MOV    CX,4        ; CHECK FOR  4  JUMPS AT START OF ROM
  309. ;
  310. CKROM:    MOV    AL,[BX]        ; READ POSSIBLE OPCODE BYTE
  311.     ADD    DI,AX        ; SUM THE TEST BYTES
  312.     ADD    BX,3        ; POINT TO POSSIBLE NEXT OPCODE
  313.     LOOP    CKROM        ; SUM THE OPCODES
  314. ;
  315.     CMP    DI,4*(0E9H)    ; SHOULD BE 4  0E9H  OPCODES (JMP)
  316. ;
  317.      IF    INTDVR
  318.     JNZ    OKEXIT        ; NO, SO LEAVE DEFAULT DRIVERS
  319.      ENDIF
  320. ;
  321.      IF    NOT INTDVR
  322.     JNZ    BDEXIT        ; NO, SO LEAVE WITH ERROR CONDITION
  323.      ENDIF
  324. ;
  325.     PUSH    CS
  326.     POP    DS        ; DS=ES=CS
  327. ;
  328.     MOV    SI,offset ROMTAB    ; POINT TO SOURCE (ROM CALL TABLE COPY)
  329.     CALL    CPYTAB        ; COPY TABLES
  330. ;
  331.     DB    FCALL
  332.     DW    3,ROMSEG    ; FAR CALL TO ROM "INIT" ROUTINE
  333. ;
  334.     MOV    AH,0        ; COMMAND FOR CARD TYPE IDENTIFY
  335. ;
  336.     DB    FCALL
  337.     DW    6,ROMSEG    ; FAR CALL TO DRIVE I/O ROM ENTRY
  338. ;
  339.     MOV    CS:CRDTYPE,AL    ; SAVE CARD TYPE []
  340. ;
  341.     OR    AL,AL        ; TEST FOR OMNINET
  342.     JNZ    OKEXIT        ; IF FLAT, EXIT
  343.     MOV    AH,4        ; SET TO FIND SERVER ADDRESS
  344.     MOV    BX,ABTCTR    ; SET ABORT TIME AND RETRYS
  345. ;
  346.     DB    FCALL
  347.     DW    6,ROMSEG    ; FAR CALL TO DRIVE I/O ROM ENTRY
  348. ;
  349.     MOV    CS:BOOTSRVR,AH    ; SAVE SERVER #
  350.     MOV    CS:SRVR,AH
  351.     OR    AL,AL        ; WAS SERVER # ACTUALLY FOUND
  352. BDEXIT:    MOV    AX,1        ; SET FOR ERROR CONDITION
  353.     JNZ    INEXIT        ; NO, SO SHOW ERROR AND EXIT
  354. ;
  355. OKEXIT:    MOV    AX,0    ; RETURN A ZERO
  356. INEXIT:    POP    ES
  357.     POP    DS
  358. ;
  359.     IF    LTYPE EQ PASCAL
  360.     RET
  361.     ENDIF
  362. ;
  363.     IF    LTYPE EQ BASIC
  364.     PUSH    BP
  365.     MOV    BP,SP
  366.     MOV    BX,6 [BP]    ; GET POINTER TO DATA "INTEGER"
  367.     MOV    [BX],AX        ; RETURN ERROR CONDITION BYTE
  368.     POP    BP
  369.     RET    2
  370.     ENDIF
  371. ;
  372. INITIO    ENDP
  373. ;
  374. ; --- COPY ADDRESS INFORMATION FROM SOURCE POINTED TO BY DS:SI ---
  375. ;
  376. CPYTAB    PROC    NEAR
  377.     MOV    DI,offset LNKTAB    ; POINT TO ROUTINE LINKAGE TABLE
  378.     MOV    CX,LENTAB        ; SET TO COPY
  379.     REP    MOVSB            ; DO COPY
  380.     RET
  381. CPYTAB    ENDP
  382. ;
  383. ; --- TEST FOR "CORVUS" CONST ][ BIOS ---
  384. ;
  385. BIOTST    PROC    NEAR
  386.     MOV    DS,AX        ; SET DATA SEGMENT TO THAT OF "BIOS"
  387.     MOV    BX,1        ; POINT TO "INIT" ADDRESS FIELD OF JUMP
  388.     MOV    BX,[BX]        ; GET THIS ADDRESS IN  BX
  389.     ADD    BX,1        ; OFFSET FOR INSTRUCTION SIZE
  390.     MOV    BX,[BX]        ; GET POSSIBLE POINTER TO "CORTAb" STRING
  391. ;
  392. BIOT1    PROC    NEAR
  393.     MOV    SI,BX        ; SAVE IT
  394.     MOV    DI,offset CORTAB    ; POINT TO LOCAL COPY OF STRING
  395.     MOV    CX,6        ; LENGTH OF STRING
  396.     REPZ    CMPSB        ; COMPARE STRINGS
  397.     STC            ; SET CARRY TO INDICATE POSSIBLE MISMATCH
  398.     JNZ    BIOE        ; EXIT IF MISMATCH
  399. ;
  400.     MOV    AX,DS        ; GET "BIOS" SEGMENT
  401.     MOV    CL,4        ; SET TO MULTIPLY BY 16
  402.     SHL    AX,CL        ; CONVERT SEGMENT # TO ADDRESS
  403.     ADD    AX,BX        ; FIND "CORTAb" ADDRESS RELATIVE TO SEG. 0
  404.     MOV    CS:CORPTR,AX    ; SAVE FOR POSSIBLE USE []
  405. ;
  406.     MOV    AL,35 [BX]    ; GET "BOOT SERVER" # FROM BIOS
  407.     MOV    CS:BOOTSRVR,AL    ; SAVE IT []
  408.     MOV    CS:SRVR,AL    ; INIT "DEFAULT SERVER" AS "BOOT SERVER" []
  409. ;
  410.     ADD    BX,23        ; OFFSET TO ROM FUNCTION TABLE POINTER
  411.     MOV    SI,[BX]        ; GET IT
  412.     CALL    CPYTAB        ; COPY TABLE INTO THIS DRIVER
  413.     MOV    AH,0        ; ID COMMAND
  414.     CALL    far ptr CRVIO    ; DO IT
  415.     MOV    CS:CRDTYPE,AL    ; SAVE CARD TYPE
  416.     CLC            ; CLEAR CARRY TO INDICATE SUCCESS
  417. BIOE:    RET            
  418. ;
  419. BIOT1    ENDP
  420. BIOTST    ENDP
  421. ;
  422. ;
  423. ; === RETURN POINTER TO "CORTAb" IN CORVUS BIOS ===
  424. ;
  425.     PUBLIC    BIOPTR
  426. ;
  427. BIOPTR    PROC    FAR
  428.     MOV    AX,CS:CORPTR    ; GET POINTER []
  429. ;
  430.     IF    LTYPE EQ PASCAL
  431.     RET
  432.     ENDIF
  433. ;
  434.     IF    LTYPE EQ BASIC
  435.     PUSH    BP
  436.     MOV    BP,SP
  437.     MOV    BX,6 [BP]    ; GET POINTER TO DATA "INTEGER"
  438.     MOV    [BX],AX        ; RETURN POINTER
  439.     POP    BP
  440.     RET    2
  441.     ENDIF
  442. ;
  443. BIOPTR    ENDP
  444. ;
  445. ; ==== SET SERVER # AND READ BOOT SERVER # ====
  446. ;
  447.     PUBLIC    SETSRVR
  448. ;
  449. SETSRVR    PROC    FAR
  450.     PUSH    BP        ; SAVE FRAME POINTER
  451.     MOV    BP,SP        ; SET NEW ONE
  452. ;
  453.     IF    LTYPE EQ PASCAL
  454.     MOV    CX,6 [BP]    ; GET PASSED VALUE
  455.     ENDIF
  456. ;
  457.     IF    LTYPE EQ BASIC
  458.     MOV    BX,6 [BP]    ; GET POINTER TO VALUE
  459.     MOV    CX,[BX]        ; GET ITS VALUE
  460.     ENDIF
  461. ;
  462.     OR    CH,CH        ; IS IT TOO BIG?
  463.     JNZ    SETS1        ; YES, SO DO NOT CHANGE PRESENT VALUE
  464.     MOV    CS:SRVR,CL    ; NO, SO SET NEW DEFAULT SERVER #
  465. SETS1:    XOR    AX,AX        ; GET A ZERO
  466.     MOV    AL,CS:BOOTSRVR    ; GET "BOOT SERVER" # AS RETURN VALUE
  467. ;
  468.     IF    LTYPE EQ BASIC
  469.     MOV    [BX],AX        ; SET RETURNED VALUE
  470.     ENDIF
  471. ;
  472.     POP    BP        ; RESTORE FRAME
  473.     RET    2
  474. SETSRVR    ENDP
  475. ;
  476. ; === FIND A VALID NETWORK SERVER ADDRESS ===
  477. ;
  478.     PUBLIC    FINDSRVR
  479. ;
  480. FINDSRVR PROC    FAR
  481.     MOV    AH,4        ; FIND SERVER COMMAND ( 1.31 bug fix )
  482.     MOV    BX,ABTCTR    ; SET MAX RETRY COUNT AND ABORT TIME
  483.     CALL    far ptr CRVIO    ; CALL I/O DRIVER
  484.     XCHG    AL,AH        ; GET SERVER # IN LSB
  485. ;
  486.     IF    LTYPE EQ PASCAL
  487.     RET
  488.     ENDIF
  489. ;
  490.     IF    LTYPE EQ BASIC
  491.     PUSH    BP
  492.     MOV    BP,SP
  493.     MOV    BX,6 [BP]    ; GET POINTER TO DATA "INTEGER"
  494.     MOV    [BX],AX        ; RETURN SERVER #
  495.     POP    BP
  496.     RET    2
  497.     ENDIF
  498. ;
  499. FINDSRVR ENDP
  500. ;
  501. ; === IDENTIFY CORVUS I/O CARD TYPE ===
  502. ;
  503.     PUBLIC    CARDID
  504. ;
  505. CARDID    PROC    FAR
  506.     MOV    AH,0        ; ZERO MSB
  507.     MOV    AL,CS:CRDTYPE    ; GET CARD IDENTIFIER
  508. ;
  509.     IF    LTYPE EQ PASCAL
  510.     RET
  511.     ENDIF
  512. ;
  513.     IF    LTYPE EQ BASIC
  514.     PUSH    BP
  515.     MOV    BP,SP
  516.     MOV    BX,6 [BP]    ; GET POINTER TO DATA "INTEGER"
  517.     MOV    [BX],AX        ; RETURN CARD TYPE
  518.     POP    BP
  519.     RET    2
  520.     ENDIF
  521. ;
  522. CARDID    ENDP
  523. ;
  524. ; === SEND/RECEIVE A COMMAND TO A NETWORK SERVER ===
  525. ;
  526.     PUBLIC    NETCMD
  527. ;
  528. NETCMD    PROC    FAR
  529.     PUSH    BP        ; SAVE FRAME POINTER
  530.     MOV    BP,SP        ; SET NEW ONE
  531. ;
  532.     IF    LTYPE EQ PASCAL
  533.     MOV    SI,6 [BP]    ; GET ADDRESS OF INPUT STRING
  534.     MOV    DI,8 [BP]    ; GET ADDRESS OF OUTPUT STRING
  535.     ENDIF
  536. ;
  537.     IF    LTYPE EQ BASIC
  538.     MOV    BX,6 [BP]    ; GET ADDRESS OF STRING DESCRIPTOR
  539.     MOV    SI,[BX]        ; GET ADDRESS OF INPUT STRING
  540.     MOV    BX,8 [BP]    ; GET ADDRESS OF STRING DESCRIPTOR
  541.     MOV    DI,[BX]        ; GET ADDRESS OF OUTPUT STRING
  542.     ENDIF
  543. ;
  544.     PUSH    DS
  545.     POP    ES        ; SET ES=DS (SAVE SEGMENT)
  546. ;
  547.     MOV    CX,[SI]        ; LOOK AT LENGTH
  548.     MOV    AL,CL        ; SAVE FOR RETURN STATUS
  549.     JCXZ    NETE        ; IF ZERO, SET RET LENGTH TO ZERO AND RET
  550. ;
  551.     PUSH    DI
  552.     INC    SI
  553.     INC    SI        ; POINT TO SEND DATA ( DS:SI )
  554. ;
  555.     INC    DI
  556.     INC    DI        ; POINT TO PLACE TO SAVE RETURNED DATA ( ES:DI)
  557. ;
  558.     MOV    DX,530        ; SET MAX # OF RETURNED BYTES
  559. ;
  560.     MOV    AH,3        ; SET FOR  SERVER CMD
  561.     MOV    AL,CS:SRVR    ; SET DISK SERVER #
  562.     MOV    BX,ABTCTR    ; SET ABORT TIME AND # OF RETRYS
  563.     CALL    far ptr CRVIO    ; DO DISK I/O
  564. ;
  565.     POP    DI        ; GET POINTER BACK TO LENGTH
  566.     MOV    CX,[DI]        ; GET LENGTH PREVIOUSLY SET
  567. NETE:    MOV    [DI],CX        ; SET LENGTH OF RETURNED STRING
  568.     MOV    AH,0        ; CLEAR MSB OF RETURNED VALUE
  569. ;
  570.     IF    LTYPE EQ PASCAL
  571.     POP    BP        ; GET FRAME POINTER BACK
  572.     RET    4        ; CLEAR RETURN STACK
  573.     ENDIF
  574. ;
  575.     IF    LTYPE EQ BASIC
  576.     MOV    BX,10 [BP]    ; GET POINTER TO DATA "INTEGER"
  577.     MOV    [BX],AX        ; RETURN ERROR CONDITION BYTE
  578.     POP    BP
  579.     RET    6
  580.     ENDIF
  581. ;
  582. NETCMD    ENDP
  583. ;
  584. ; === RECEIVE A STRING OF BYTES FROM THE DRIVE ===
  585. ;
  586.     PUBLIC    CDRECV, DRVRECV
  587. ;
  588. CDRECV    PROC    FAR
  589. DRVRECV:
  590.     PUSH    BP        ; SAVE FRAME POINTER
  591.     MOV    BP,SP        ; SET NEW ONE
  592. ;
  593.     IF    LTYPE EQ PASCAL
  594.     MOV    DI,6 [BP]    ; GET ADDRESS OF STRING TO SAVE DATA IN
  595.     ENDIF
  596. ;
  597.     IF    LTYPE EQ BASIC
  598.     MOV    BX,6 [BP]    ; GET ADDRESS OF STRING DESCRIPTOR
  599.     INC    BX
  600.     INC    BX        ; POINT TO STRING POINTER
  601.     MOV    DI,[BX]        ; GET ADDRESS OF STRING TO SAVE DATA IN
  602.     ENDIF
  603. ;
  604.     PUSH    DS
  605.     POP    ES        ; SET ES=DS (SAVE SEGMENT)
  606. ;
  607.     PUSH    DI
  608.     PUSH    DS
  609. ;
  610.     LDS    SI,CS:dword ptr SNDPTR ; GET POINTER TO SOURCE STRING
  611.     MOV    CX,[SI]        ; LOOK AT LENGTH
  612.     MOV    AL,CL        ; SAVE FOR RETURN STATUS
  613.     JCXZ    RLPE        ; IF ZERO, SET RET LENGTH TO ZERO AND RET
  614. ;
  615.     INC    SI
  616.     INC    SI        ; POINT TO SEND DATA ( DS:SI )
  617. ;
  618.     INC    DI
  619.     INC    DI
  620.     INC    DI        ; POINT TO PLACE TO SAVE RETURNED DATA ( ES:DI)
  621. ;
  622.     MOV    DX,530        ; SET MAX # OF RETURNED BYTES
  623. ;
  624.     MOV    AH,1        ; SET FOR "BCI" LIKE COMMAND
  625.     MOV    AL,CS:SRVR    ; SET DISK SERVER #
  626.     MOV    BX,ABTCTR    ; SET ABORT TIME AND # OF RETRYS
  627.     CALL    far ptr CRVIO    ; DO DISK I/O
  628. ;
  629. RLPE:    POP    DS
  630.     POP    DI        ; GET POINTER BACK TO LENGTH
  631.     MOV    [DI],CX        ; SET LENGTH OF RETURNED STRING
  632.     MOV    2 [DI],AL    ; SAVE RETURN STATUS
  633.     POP    BP        ; GET FRAME POINTER BACK
  634.     RET    2        ; CLEAR RETURN STACK
  635. CDRECV    ENDP
  636. ;
  637. ; === SEND STRING OF BYTES TO DRIVE ===
  638. ;
  639. ;    THIS CONSTELLATION VERSION
  640. ;    JUST SAVES TWO POINTERS TO 
  641. ;    THE DATA STRING TO SEND.  THE
  642. ;    CDRECV  ROUTINE ACTUALLY SENDS
  643. ;    THE DATA AND RECEIVES THE
  644. ;    RETURN STATUS
  645. ;
  646.     PUBLIC    CDSEND, DRVSEND
  647. ;
  648. CDSEND    PROC    FAR
  649. DRVSEND:
  650.     PUSH    BP        ; SAVE FRAME POINTER
  651.     MOV    BP,SP        ; SET NEW ONE
  652. ;
  653.     IF    LTYPE EQ PASCAL
  654.     MOV    AX,6 [BP]    ; GET ADDRESS OF STRING TO SEND
  655.     ENDIF
  656. ;
  657.     IF    LTYPE EQ BASIC
  658.     MOV    BX,6 [BP]    ; GET ADDRESS OF STRING DESCRIPTOR
  659.     INC    BX
  660.     INC    BX        ; POINT TO STRING POINTER
  661.     MOV    AX,[BX]        ; GET ADDRESS OF STRING TO SAVE DATA IN
  662.     ENDIF
  663. ;
  664.     MOV    CS:SNDPTR,AX    ; SAVE IT []
  665. ;
  666.     MOV    AX,DS        ; GET DATA SEGMENT
  667.     MOV    CS:SNDSEG,AX    ; SAVE IT []
  668. ;
  669.     POP    BP        ; GET FRAME POINTER BACK
  670.     RET    2        ; CLEAR RETURN STACK
  671. CDSEND    ENDP
  672. ;
  673. ;
  674. ;
  675. ;
  676. ; ============ FLAT CABLE R/W ROUTINES ==============
  677. ;
  678. ;  THESE ROUTINES ARE ESSENTIALLY THE SAME AS THE FLAT CABLE
  679. ;  DRIVERS IN THE "ROM".  THEY ARE REPRODUCED HERE SO THAT
  680. ;  SYSTEMS WITH FLAT CABLE INTERFACES NEED NOT HAVE A "ROM"
  681. ;  TO WORK WITH  CONSTELLATION ][  SOFTWARE.
  682. ;
  683. ; --- BUFFERS USED BY "ROM" DRIVER ROUTINES ---
  684. ;
  685. CLICKS    DB    0        ; BUFFER FOR SAVING # OF CLOCK TICKS
  686. STOPTM    DW    0        ; BUFFER FOR SAVING STOP TIME
  687. RMCMD    DB    0        ; BUFFER FOR SAVING PASSED "ROM" CMD
  688. BLKLEN    DW    512        ; BUFFER FOR SAVING # OF BYTES TO XFER
  689. CMDLEN    DW    4        ; BUFFER FOR SAVING LENGTH OF CMD
  690. RTNCODE DB    0        ; BUFFER FOR SAVING DISK RETURN CODE
  691. ;
  692. ; --- SET TIMER ---
  693. ;
  694. STIME    PROC    NEAR
  695.     XOR    AH,AH        ; READ TIME OF DAY CLOCK
  696.     INT    1AH
  697.     JMP    STIME1
  698. ;
  699. ; --- CHECK FOR TIMOUT ---
  700. ;
  701. CKTIME:    CMP    CS:CLICKS,0    ; WAS A WAIT REQUESTED? []
  702.     CLC
  703.     JZ    CKRET        ; NO, SO RETURN WITH CARRY CLEAR
  704.     XOR    AH,AH        ; TIME OF DAY CALL
  705.     INT    1AH
  706.     OR    AL,AL        ; HAS CLOCK WRAPPED AROUND TO ZERO?
  707.     JZ    CKT1        ; NO
  708. ;
  709. ;  IF CLOCK HAS PASSED 24 HOURS, RECALCULATE STOP TIME
  710. ;
  711. STIME1:    MOV    AL,CS:CLICKS    ; GET # OF CLOCK TICKS OF DELAY []
  712.     XOR    AH,AH
  713.     MOV    CL,4        ; SET TO MULTIPLY BY 16
  714.     SHL    AX,CL        ; DO IT BY SHIFTING
  715.     ADD    DX,AX
  716.     MOV    CS:STOPTM,DX    ; SAVE STOP TIME []
  717. CHKOK:    CLC            ; CLEAR CARRY ( TIME CHECK IS OK )
  718.     RET
  719. ;
  720. CKT1:    CMP    DX,CS:STOPTM    ; TIMEOUT? []
  721.     JB    CHKOK
  722. ;
  723.     STC            ; SET CARRY IF TIMEOUT
  724. CKRET:    RET
  725. STIME    ENDP
  726. ;
  727. ; ---- MAIN DRIVER ENTRY POINT ---
  728. ;
  729. DRVIO    PROC    FAR
  730.     CLD            ; SET FOR "INCREMENT"
  731.     MOV    CS:RMCMD,AH    ; SAVE COMMAND []
  732.     CMP    AH,1        ; IS IT A "BCI" COMMAND?
  733.     JZ    RW15        ; YES, SO DO IT
  734.     CMP    AH,5        ; IS IT A "WRITE" COMMAND?
  735.     JZ    RW15        ; YES, SO DO IT
  736.     CMP    AH,0        ; IS IT AN "IDENTIFY" COMMAND
  737.     JZ    RW00        ; YES, SO DO IT
  738.     MOV    AL,0FFH        ; IF ANY OTHER, INDICATE "ABORT" OR ERROR
  739.     MOV    CX,0        
  740.     RET            ; LONG RET
  741. ;
  742. RW00:    MOV    AL,1        ; INDICATE FLAT CABLE
  743.     RET            ; LONG RET
  744. ;
  745. ;
  746. RW15:    PUSH    DS        ; SAVE REGISTERS THAT MAY BE CHANGED
  747.     PUSH    SI
  748.     PUSH    DI
  749.     PUSH    BX
  750.     PUSH    DX
  751. ;
  752.     MOV    CS:CLICKS,BL    ; SAVE # OF TIMER CLICKS  []
  753.     MOV    CS:BLKLEN,DX    ; SAVE BLOCK LENGTH []
  754.     MOV    CS:CMDLEN,CX    ; SAVE CMD LENGTH []
  755.     CALL    ROMIO        ; DO DISK I/O
  756.     POP    DX
  757.     POP    BX
  758.     POP    DI
  759.     POP    SI
  760.     POP    DS
  761.     MOV    AL,CS:RTNCODE    ; GET RETURN CODE []
  762. ;
  763. DMYLRET    LABEL    FAR
  764.     RET            ; LONG RET
  765. ;
  766. DMYIRET    LABEL    FAR
  767.     IRET            ; DUMMY  "IRET"
  768. ;
  769. DRVIO    ENDP
  770. ;
  771. ROMIO    PROC    NEAR
  772.     CALL    STIME        ; SETUP TIMER COUNT
  773. ;
  774. RO1:    MOV    DX,STAT        ; POINT TO STATUS PORT
  775.     CLI            ; DISABLE INTERRUPTS FOR TEST
  776.     IN    AL,DX        ; READ DRIVE STATUS BYTE
  777.     TEST    AL,DRDY        ; IS IT READY?
  778.     JZ    RO2        ; YES, SO PROCEED
  779.     STI            ; NO, SO RE-ENABLE INTERRUPTS
  780.     CALL    CKTIME        ; CHECK IF TIMED OUT
  781.     JNC    RO1        ; IF NOT, TRY AGAIN
  782. ARET:    MOV    CS:RTNCODE,0FFH ; IF TIMED OUT, SET ERROR []
  783.     MOV    CX,0        ; INDICATE NO DATA RETURNED
  784.     RET
  785. ;
  786. RO2:    MOV    CX,CS:CMDLEN    ; GET CMD LENGTH []
  787.     CALL    SNDBLK        ; SEND BLOCK OF DATA TO DRIVE
  788.     CMP    CS:RMCMD,5    ; WAS CMD A "WRITE" CMD? []
  789.     JNZ    RCVBLK        ; NO, SO GO RECEIVE DATA
  790. ;
  791.     MOV    SI,DI        ; YES, POINT TO SECTOR DATA
  792.     MOV    AX,ES
  793.     MOV    DS,AX
  794.     MOV    CX,CS:BLKLEN    ; GET LENGTH OF DATA BLOCK []
  795.     CALL    SNDBLK        ; SEND SECTOR DATA
  796. ;
  797. RCVBLK:    CALL    STIME        ; SET TIMER
  798. ;
  799.     CALL    DELAY1        ; DELAY
  800. ;
  801. RCV1:    CALL    CKTIME        ; TIMED OUT YET?
  802.     JC    ARET        ; YES, SO RETURN WITH ERROR
  803. ;
  804. RCV2:    MOV    DX,STAT        ; POINT TO STATUS PORT
  805.     IN    AL,DX        ; READ DRIVE STATUS BYTE
  806.     TEST    AL,DIFAC    ; TEST BUS DIRECTION
  807.     JNZ    RCV1        ; WAIT FOR "HOST TO DRIVE"
  808.     TEST    AL,DRDY        ; TEST IF ALSO READY
  809.     JNZ    RCV1
  810. ;
  811.     CALL    DELAY1        ; WAIT TO BE SURE
  812. ;
  813.     IN    AL,DX        ; TEST STATUS AGAIN
  814.     TEST    AL,DIFAC        
  815.     JNZ    RCV1        ; IF FALSE ALARM, TRY AGAIN
  816.     TEST    AL,DRDY
  817.     JNZ    RCV1        ; IF NOT READY, TRY AGAIN
  818. ;
  819.     DEC    DX        ; POINT TO DATA PORT
  820.     IN    AL,DX        ; GET RETURN CODE
  821.     INC    DX        ; POINT BACK TO STATUS PORT
  822. ;
  823.     MOV    CX,1        ; INDICATE 1 BYTE WAS RETURNED
  824.     MOV    CS:RTNCODE,AL    ; SAVE IT []
  825.     CMP    CS:RMCMD,5    ; WAS CMD A "WRITE" CMD []
  826.     JZ    RCRET        ; YES, SO RETURN
  827. ;
  828.     MOV    BX,CX        ; OTHERWISE SET COUNTER
  829.     MOV    CX,CS:BLKLEN    ; GET LENGTH OF EXPECTED DATA
  830. ;
  831. RCV3:    IN    AL,DX        ; GET STATUS AGAIN
  832.     TEST    AL,DRDY        ; IS DRIVE READY?
  833.     JNZ    RCV3        ; NO, SO WAIT
  834.     TEST    AL,DIFAC    ; ARE WE DONE?
  835.     JNZ    RCV4        ; POSSIBLY, ...
  836. ;
  837.     DEC    DX        ; POINT TO DATA PORT
  838.     IN    AL,DX        ; GET DATA FROM DRIVE
  839.     INC    DX        ; POINT BACK TO STATUS PORT
  840. ;
  841.     JCXZ    RCVS        ; IF DATA NOT WANTED
  842.     STOSB            ; SAVE DATA IN BUFFER
  843.     DEC    CX        ; COUNT DOWN # TO SAVE
  844. ;
  845. RCVS:    INC    BX        ; COUNT UP # RECEIVED
  846.     JMP    RCV3        ; LOOP UNTIL EXIT
  847. ;
  848. RCV4:    IN    AL,DX        ; GET STATUS BYTE
  849.     TEST    AL,DRDY        ; IS DRIVE READY
  850.     JNZ    RCV3        ; NO, SO PREVIOUS RESULT MAY BE FALSE
  851.     TEST    AL,DIFAC    ; IS IT STILL "HOST TO DRIVE"?
  852.     JZ    RCV3        ; NO, SO TRY AGAIN
  853. ;
  854.     MOV    CX,BX        ; GET # OF BYTES RECEIVED
  855. RCRET:    RET
  856. ;
  857. DELAY1:    MOV    BL,15        ; SET DELAY
  858. DELAY:    DEC    BL
  859.     JNZ    DELAY        ; LOOP UNTIL DONE
  860.     RET
  861. ;
  862. ; --- SEND BLOCK OF DATA TO DRIVE ---
  863. ;
  864. SNDBLK:    MOV    DX,STAT        ; POINT TO STATUS PORT
  865. ;
  866. SND1:    IN    AL,DX        ; GET STATUS BYTE
  867.     TEST    AL,DRDY        ; IS DRIVE READY?
  868.     JNZ    SND1        ; NO, SO LOOP
  869. ;
  870.     DEC    DX        ; POINT TO DATA PORT
  871.     LODSB            ; GET DATA FROM MEMORY
  872.     OUT    DX,AL        ; SEND DATA TO DRIVE
  873.     INC    DX        ; POINT BACK TO STATUS PORT
  874. ;
  875.     STI            ; RE-ENABLE INTERRUPTS
  876.     LOOP    SND1        ; CONTINUE UNTIL DONE
  877.     RET
  878. ;
  879. ROMIO    ENDP
  880. ;
  881. ;
  882. ; ---- INTERFACE "FAR" CALL TABLE ---
  883. ;    THIS TABLE GETS PATCHED
  884. ;    TO EITHER "BIOS" CALLS OR
  885. ;    "ROM" CALLS IF THE APPROPRIATE
  886. ;           LINK IS FOUND
  887. ;
  888. LNKTAB    PROC    NEAR
  889.     JMP    DMYLRET        ;
  890.     JMP    DMYLRET        ;
  891. ;
  892. CRVIO    LABEL    FAR
  893.     JMP    DRVIO        ; THIS SHOULD BE A FAR CALL
  894. ;
  895.     JMP    DMYIRET        ; THIS SHOULD BE A FAR JUMP
  896. LNKTAB    ENDP
  897. ;
  898. ; =========================================================
  899. ;
  900. PGSEG    ENDS
  901. ;
  902. ;
  903.     END
  904.